import { PL0SemanticError, TokenType } from "./Constrants"
import { Token } from "./Token"

export enum idfType { const, var, proc }
export type IDF = {
    type: idfType,
    value: string,
    isGlobal: boolean,
    num?: number 
}

export class TreeNode {
    parent: TreeNode | null = null
    children: TreeNode[]
    constructor(children: TreeNode[] = []) {
        this.children = children
    }

    insertChild(child: TreeNode | null) {
        if (!child) return
        this.children.push(child)
        child.parent = this
    }

    find(test: Function): any {
        let node: TreeNode | null = this
        while (node) {
            const t = test(node)
            if (t != null) {
                return t
            }
            node = node.parent
        }
        return null
    }

    route() {
        const route = []
        let parent: TreeNode | null = this.parent
        let node: TreeNode | null = this
        while (parent) {
            const i = parent.children.indexOf(node)
            route.unshift(i)
            node = parent
            parent = parent.parent
        }
        return route
    }

    /**
     * 通过该树上方ProcedureNode存在
     * @returns 不存在为true
     */
    isGlobal(): boolean {
        let parent: TreeNode | null = this.parent
        while (parent) {
            if (parent instanceof ProcedureNode) return parent.isGlobal()
            parent = parent.parent
        }
        return true
    }

    toWat(...args: any[]):string[] {
        return []
    }
}

export class ProcedureNode extends TreeNode {
    constrants: Map<string, number> = new Map()
    variables: Map<string, number> = new Map()
    procedures: Map<string, ProcedureNode> = new Map()
    /**
     *
     */
    constructor(parent: ProcedureNode | null, idf: Token | null, children = []) {
        super(children);
        if (parent && idf) {
            parent.pushProc(idf.value, this)
            this.parent = parent
        }

    }

    isGlobal() {
        let parent: TreeNode | null = this.parent
        while (parent) {
            if (parent instanceof ProcedureNode) return false
            parent = parent.parent
        }
        return true
    }

    pushConst(key: string, number: number) {
        const hasKey = this.constrants.has(key) || this.variables.has(key)
        if (hasKey) throw PL0SemanticError.raise(2, key)
        this.constrants.set(key, number)

    }

    pushVar(key: string) {
        const hasKey = this.constrants.has(key) || this.variables.has(key)
        if (hasKey) throw PL0SemanticError.raise(3, key)
        this.variables.set(key, 0)
    }

    pushProc(key: string, proc: ProcedureNode) {
        const hasKey = this.procedures.has(key)
        if (hasKey) throw PL0SemanticError.raise(1, key)
        this.procedures.set(key, proc)
    }

    toWat() {
        const head = [
            '(module',
            '(memory (import "js" "mem") 1) ;; 读取内存表'
        ]
        let vars: string[] = [], procs: string[] = [], seqs: string[] = []
        if (this.isGlobal()) {
            this.variables.forEach((value, key) => {
                vars.push(`(global $${key} (mut i32) (i32.const 0))`)
            })
            this.procedures.forEach((value, key) => {
                vars.push(`(func $${key}`)
                vars = vars.concat(value.toWat())
                vars.push(`)`)
            })
            this.children.forEach((node) => {
                seqs = seqs.concat(node.toWat())
            })
            // seqs
            return head.concat(vars)
                    .concat(procs)
                    .concat(['(func (export "program")'])
                    .concat(seqs)
                    .concat([
                        ')',
                        ')'
                    ])
        } else {
            this.variables.forEach((value, key) => {
                vars = vars.concat([
                    `(local $${key} i32)`,
                    `i32.const 0`,
                    `local.set $${key}`
                ])
            })
            this.procedures.forEach((value, key) => {
                // 暂时不支持多层 procedure 嵌套，尚未实现变量多层嵌套读取
                procs = procs.concat([
                    `(func $${key}`
                ]).concat(value.toWat())
                .concat([')'])
            })
            this.children.forEach((node) => {
                seqs = seqs.concat(node.toWat())
            })
            return vars.concat(seqs)
        }
    }
}

export enum SequenceType { assign, branch, while, call, read, write, combine, repeat }

export class SequenceNode extends TreeNode {
    type: SequenceType
    idf: IDF | null = null
    idfs: IDF[] = []
    /**
     *
     */
    constructor(t: SequenceType, parent: TreeNode | null, children = []) {
        super(children)
        if (parent) parent.insertChild(this)
        this.type = t
    }

    toWat() {
        let code: string[] = []   // 可以用来选择操作的字符串
        const route = this.route().join('_')
        const global = this.idf?.isGlobal ? 'global' : 'local'
        switch (this.type) {
            case SequenceType.assign:
                return this.children[0].toWat()
                .concat([`${global}.set $${this.idf?.value}`])
            case SequenceType.branch:
                // [<condition>, <sequence>, else <sequence>]
                code = this.children[0].toWat()
                .concat(['if'])
                .concat(this.children[1].toWat())
                if (this.children[2]) {
                    code = code.concat(['else'])
                    .concat(this.children[2].toWat())
                }
                code = code.concat(['end'])
                return code
            case SequenceType.while:
                return [`loop $while${route}`]
                .concat(this.children[0].toWat())
                .concat(['if'])
                .concat(this.children[1].toWat())
                .concat([`br $while${route}`, `end`, 'end'])
            case SequenceType.call:
                return [`call $${this.idf?.value}`]
            case SequenceType.read:
                this.idfs.forEach((idf: IDF) => {
                    const global = idf.isGlobal ? 'global' : 'local'
                    code = code.concat([
                        `i32.const 252`, 'i32.load', 'i32.load',
                        `${global}.set $${idf.value}`,
                        'i32.const 252', 'i32.const 252', 'i32.load', 'i32.const 4', 'i32.add', 'i32.store'
                    ]) 
                })
                return code
            case SequenceType.write:
                this.idfs.forEach((idf: IDF) => {
                    const global = idf.isGlobal ? 'global' : 'local'
                    const getValue = idf.num === undefined ? `${global}.get $${idf.value}` : `i32.const ${idf.num}`
                    code = code.concat([
                        `i32.const 508`, 'i32.load', 'i32.const 256', 'i32.add',
                        getValue,
                        'i32.store', 'i32.const 508', 'i32.const 508', 'i32.load', 'i32.const 4', 'i32.add', 'i32.store'
                    ]) 
                })
                return code
            case SequenceType.combine:
                // [...<sequence>]
                this.children.forEach((node) => {
                    code = code.concat(node.toWat())
                })
                return code
            case SequenceType.repeat:
                // [<condition>, ...<sequence>]
                for (let i = 0; i < this.children.length-1; i++) {
                    code = code.concat(this.children[i].toWat())
                }
                return [`loop $repeat${route}`]
                .concat(code)
                .concat(this.children[this.children.length-1].toWat())  // condition
                .concat([`i32.eqz`, `br_if $repeat${route}`, 'end'])
        }
    }
}

export class ConditionNode extends TreeNode {
    type = TokenType.ODD    // 关系符号属性
    /**
     *
     */
    constructor(parent: TreeNode | null, children = []) {
        super(children);
        if (parent) parent.insertChild(this)

    }

    toWat() {
        // 关系运算符对应指令名称
        const relation: any = {}
        relation[TokenType.EQL] = 'eq'
        relation[TokenType.NEQ] = 'ne'
        relation[TokenType.LES] = 'lt_s'
        relation[TokenType.LEQ] = 'le_s'
        relation[TokenType.GTR] = 'gt_s'
        relation[TokenType.GEQ] = 'ge_s'
        if (this.type === TokenType.ODD) {
            return this.children[0].toWat()
            .concat(['i32.const 2', 'i32.rem_s', 'i32.const 1', 'i32.eq'])
        } else {
            return this.children[0].toWat()
            .concat(this.children[1].toWat())
            .concat([`i32.${relation[this.type]}`])
        }
    }
}

export class ExpNode extends TreeNode {
    /**
     *
     */
    constructor(parent: TreeNode | null, children = []) {
        super(children);

        if (parent) parent.insertChild(this)
    }

    toWat() {
        const children = this.children as ItemNode[]
        let code: string[] = ['i32.const 0']
        children.forEach((node: ItemNode) => {
            const op = node.isAdden ? 'i32.add' : 'i32.sub'
            code = code.concat(node.toWat())
            .concat([op])
        })
        return code
    }
}

export class ItemNode extends TreeNode {
    isAdden = false
    isMul: boolean[] = []
    /**
     *
     */
    constructor(parent: TreeNode | null, isAdden: boolean, children = []) {
        super(children);
        this.isAdden = isAdden
        if (parent) parent.insertChild(this)
    }

    toWat() {        
        const children = this.children as FactorNode[]
        let code: string[] = []
        this.isMul.forEach((isMul: boolean, i: number) => {
            const node = children[i+1]
            const op = isMul ? 'i32.mul' : 'i32.div_s'
            code = code.concat(node.toWat()).concat([op])
        })
        return this.children[0].toWat().concat(code)
    }
}

export class FactorNode extends TreeNode {
    idf: IDF | null = null
    value: number | null = null
    /**
     *
     */
    constructor(parent: TreeNode | null, children = []) {
        super(children);

        if (parent) parent.insertChild(this)
    }

    toWat() {
        if (this.value != null) {
            return [`i32.const ${this.value}`]
        } else if (this.idf) {
            const global = this.idf.isGlobal ? 'global' : 'local'
            return [`${global}.get $${this.idf.value}`]
        } else {
            return this.children[0].toWat()
        }
        
    }
}
